#include "insertsort.h"
#include <QScopeGuard>

InsertSort::InsertSort(QObject *parent) : SortBase{parent}
{
    animation.setStartValue(0.0);
    animation.setEndValue(1.0);
    animation.setEasingCurve(QEasingCurve::OutQuart);
    animation.setLoopCount(1);
    connect(&animation, &QVariantAnimation::finished, &loop, &QEventLoop::quit);
    connect(&animation, &QVariantAnimation::valueChanged, this, &SortBase::updateRequest);
}

void InsertSort::sort(size_t num_of_elem, int interval)
{
    // ensure calling setRunState(false) and update at the end of function
    auto guard = qScopeGuard([this] {
        setRunState(false);
        emit updateRequest();
    });

    stop();
    if (arr.size() != num_of_elem)
        randomArray(num_of_elem);
    setRunState(true);

    this->interval = interval;

    size_t &i = sortedRound, &j = currentIndex;
    for (i = 1; i < num_of_elem; i++) {
        if (arr[i] < arr[i - 1]) {
            size_t tmp = arr[i];
            for (j = i - 1; (int) j >= 0 && arr[j] > tmp; j--) {
                animation.setDuration(this->interval * 3);
                animation.start();
                swapFlag = true;
                loop.exec();
                if (runState()) {
                    std::swap(arr[j + 1], arr[j]);
                    swapFlag = false;
                } else {
                    return;
                }

                emit updateRequest();
            }
            arr[j + 1] = tmp;
        } else {
            j = 0x3fffffff; // This is set to prevent wrong coloring
            animation.setDuration(this->interval);
            animation.start();
            swapFlag = true;
            loop.exec();
            if (!runState()) {
                return;
            }
            emit updateRequest();
        }
    }
}

void InsertSort::stop()
{
    setRunState(false);
    animation.stop();
    loop.quit();
    emit updateRequest();
}

void InsertSort::draw(QPainter *painter, int width, int height)
{
    painter->setPen(QColor(200, 200, 200));
    const size_t len = arr.size();
    const size_t max_elem = *(std::max_element(arr.begin(), arr.end()));

    // border
    constexpr int left_space = 0;
    constexpr int right_space = 0;
    constexpr int top_space = 20;
    constexpr int bottom_space = 50;

    // space width between two column
    constexpr int item_space = 10;

    const int text_height = painter->fontMetrics().height();

    // distance between column and number text
    constexpr int text_space = 15;

    // item width is dynamically adjusted according to the window width
    const double item_width = (width + item_space - left_space - right_space) / (double) len
                              - item_space;
    const double item_bottom = height - bottom_space;

    // item height is also dynamically adjusted according to the window height
    const double height_factor = (height - top_space - bottom_space) / (double) max_elem;

    // current drawing column width and height
    double item_left = 0;
    double item_height = 0;

    QColor color;
    for (size_t i = 0; i < len; i++) {
        // position of the column
        item_left = left_space + i * (item_width + item_space);
        item_height = height_factor * arr.at(i);

        // color of the column
        color = QColor(200, 200, 200);

        // current comparing column
        if (runState()) {
            if (i <= sortedRound) {
                // sorted number (Not necessarily at it's final position)
                color = QColor(0, 170, 0);
            }
            if (i == currentIndex) {
                color = QColor(255, 170, 0);
                if (swapFlag) {
                    item_left += animation.currentValue().toDouble() * (item_width + item_space);
                }
            }
            if (i == currentIndex + 1) {
                // In bubble sort, bigger number are sorted first
                color = QColor(0, 170, 255);
                if (swapFlag) {
                    item_left -= animation.currentValue().toDouble() * (item_width + item_space);
                }
            }
        }

        // draw text
        painter->drawText(item_left,
                          item_bottom + text_height + text_space,
                          QString::number(arr.at(i)));

        // draw rect
        painter->fillRect(item_left, item_bottom - item_height, item_width, item_height, color);
    }
}

void InsertSort::skip()
{
    this->interval = 1;
}
